package br.com.sendmail.operacoes;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.StringTokenizer;

import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;

import br.com.sendmail.modelo.ContaEmail;
import br.com.sendmail.modelo.Destinatarios;
import br.com.sendmail.modelo.Template;

/**
 * 30/11/2012 - SendMail.java 
 * Elton Souza - eltonds88@gmail.com
 * 
 * Classe principal para o envio de emails usando a API SendMail
 *
 */
public class SendMail implements Serializable, Runnable {

	  private static final long serialVersionUID = 1L;
    public static final String ARQUIVO_CONF = "/WEB-INF/send-mail.xml";

    private static Contas contas = null;
    private static Templates templates = null;

    private ContaEmail conta = null;
    private Template template = null;
    private Destinatarios destinatarios;
    private String conteudo;
    private String assunto;
    private String mimeType;
    private String[] tokens;
    private String[] tokensAssunto;
    private String delimitador;

    private ServletContext context;
    private ServletRequest request;

    /**
     * Construtor basico para o envio de email
     * @param request               A requisicao HTTP
     * @param nomeConta             O nome da conta que deverá ser utilizado (deve estar cadastrado no send-mail.xml)
     * @throws Exception    Lancada quando nao se encontra a conta passada por parametro.
     */
    public SendMail(ServletRequest request, String nomeConta) throws Exception {
    	this(request, nomeConta, null);
    }

    /**
     * Construtor intermediario para o envio de email
     * @param context               A requisicao HTTP
     * @param nomeConta             O nome da conta que deverá ser utilizado (deve estar cadastrado no send-mail.xml)
     * @param destinatarios Os destinatarios do email
     * @throws Exception    Lancada quando nao se encontra a conta passada por parametro.
     */
    public SendMail(ServletRequest request, String nomeConta, Destinatarios destinatarios) throws Exception {
    	this(request, nomeConta, destinatarios, null);
    }

    /**
     * Construtor para o envio de email usando a API
     * @param context               A requisicao HTTP
     * @param nomeConta             O nome da conta que deverá ser utilizado (deve estar cadastrado no send-mail.xml)
     * @param destinatarios Os destinatarios do email
     * @param template              O nome do template a ser utilizado para o envio do email (deve estar cadastrado no send-mail.xml)
     * @throws Exception    Lancada quando nao se encontra a conta passada por parametro.
     */
    public SendMail(ServletRequest request, String nomeConta, Destinatarios destinatarios, String template) throws Exception {
        this.request = request;
		this.context = request.getServletContext();
        //verifica se as contas ja estao carregadas (se a classe ja foi usada antes)
        //somente carrega as contas e templates quando a classe e usada pela primeira vez
        //para nao alocar memoria desnecessariamente
        if(contas == null) {
                contas = new Contas(this.context);
        }
        //busca a conta passada por parametro
        this.conta = Contas.getConta(nomeConta);
        //se a conta nao e encontrada lanca uma excecao
        if(conta == null) {
                throw new Exception("Conta de email nao localizada");
        }
        //caso seja especificado um nome de template como parametro, verifica se os templates
        //ja foram carregados
        if(template != null && templates == null) {
                templates = new Templates(this.context);
        }
        //caso seja especificado um nome de template como parametro, verifica se os templates
        if(template != null) {
                this.template = Templates.getTemplate(template);
                //Caso nao encontre o template passado por parametro, lanca um excessao
                if(this.template == null) {
                        throw new Exception("Conta de email nao localizada");
                }
        }

        this.destinatarios = destinatarios;
        this.delimitador = ";";

    }

    public void setConteudo(String conteudo) {
    	this.conteudo = conteudo;
    }

    public void setAssunto(String assunto) {
    	this.assunto = assunto;
    }

    public void setDestinatarios(Destinatarios destinatarios) {
    	this.destinatarios = destinatarios;
    }

    /**
     * <pre>
     * Metodo para definicao dos campos personalizados e seus valores a serem substituidos no template/conteudo<br>
     * Usa pares de Strings (token;valor) com o delimitador padrão sendo o ;<br>
     * Exemplo de uso: Conteudo do email: "Senhor %nome%, sua senha e %senha%"<br>
     * Chamada ao metodo: setTokens ("%nome%;elton","%senha%;123456")<br>
     * A saida: "Senhor elton, sua senha é 123456"
     * </pre>
     * @param tokens O pares de string "%token%;valor" que devem ser substituidos no conteudo
     */
    public void setTokens(String ... tokens) {
    	this.tokens = tokens;
    }

    /**
     * <pre>
     * Metodo para definicao dos campos personalizados e seus valores a serem substituidos no assunto do email<br>
     * Usa pares de Strings (token;valor) com o delimitador padrão sendo o ;<br>
     * Exemplo de uso: Assunto do email: "Senhor %nome%, parabens pela compra nro %nro_compra%"<br>
     * Chamada ao metodo: setTokens ("%nome%;elton","%nro_compra%;654321")<br>
     * A saida: "Senhor elton, parabens pela compra nro 654321"
     * </pre>
     * @param tokens O pares de string "%token%;valor" que devem ser substituidos no assunto 
     */
    public void setTokensAssunto(String ... tokensAssunto) {
    	this.tokensAssunto = tokensAssunto;
    }

    public void setDelimitador(String delimitador) {
    	this.delimitador = delimitador;
    }

    public void enviar() {
        //Se ha um template especificado, carrega o conteudo do template
        if(template != null) {
        	carregarConteudo();
        } else {
            mimeType = "text/html";
        }
        replaceTokens();
        setDados();
    }

    @Override
    public void run() {
        if(conta != null) {
        	enviar();
        } else {
        	System.out.println("Conta de email não localizada");
        }
    }

    private void carregarConteudo () {
        conteudo = "";
        assunto = template.getAssuntoEmail();
        mimeType = template.getTipo();
        try {
            //Busca o conteudo do arquivo especificado (que deve estar no contexto da aplicacao)
            FileInputStream stream = new FileInputStream(context.getRealPath(template.getPath()));
            InputStreamReader reader = new InputStreamReader(stream);
            BufferedReader br = new BufferedReader(reader);
            String linha = br.readLine();
            while(linha != null) {
               conteudo += linha;
               linha = br.readLine();
            }
            br.close();
            reader.close();
            stream.close();
        } catch (Exception e) {
                e.printStackTrace();
        }
    }

    private void replaceTokens() {
        assunto = replace(assunto, tokensAssunto);
        conteudo = replace(conteudo, tokens);
        if(template.getPathImagens() != null) {
        	conteudo = replace(conteudo, Template.TOKEN_PATH_IMG+delimitador+getHost()+template.getPathImagens());
        }
    }

    private String replace(String content, String ... tks) {
        String tk;
        String val;
        StringTokenizer st;
        if(tks != null) {
            for(String s : tks) {
                    st = new StringTokenizer(s, delimitador);
                while (st.hasMoreTokens()) {
                    tk = st.nextToken();
                    val = st.nextToken();
                    content = content.replaceAll(tk, val);
                }
            }
        }
        return content;
    }

    private void setDados() {

	    Session session = null;
    	if(conta.isUsaAutenticacao()) {
    		session = Session.getInstance(conta.getPropriedades(),
    			  new javax.mail.Authenticator() {
    				protected PasswordAuthentication getPasswordAuthentication() {
    					return new PasswordAuthentication(conta.getUsuario(), conta.getSenha());
    				}
    			  }
    		);
    	} else {
    		session = Session.getDefaultInstance(conta.getPropriedades());
    	}
    	session.setDebug(conta.isDebug());
      try {
          // Create a default MimeMessage object.
          MimeMessage message = new MimeMessage(session);

          //Define o 
          InternetAddress address = new InternetAddress(conta.getFromEmail());
          address.setPersonal(conta.getFromNome());
          message.setFrom(address);

          //Define os destinatarios
          for(String s : destinatarios.getTo()) {
             message.addRecipient(Message.RecipientType.TO, new InternetAddress(s));
          }
          if(conta.getCopyTo() != null) {
             message.addRecipient(Message.RecipientType.CC, 
                new InternetAddress(conta.getCopyTo()));
          }
          if(destinatarios.getCc() != null) {
              for(String s : destinatarios.getCc()) {
                  message.addRecipient(Message.RecipientType.CC, 
                     new InternetAddress(s));
              }
          }

          if(destinatarios.getCco() != null) {
              for(String s : destinatarios.getCco()) {
                 message.addRecipient(Message.RecipientType.BCC,
                    new InternetAddress(s));
              }
          }

          //Define o assunto
          message.setSubject(assunto == null ? "Sem assunto" : assunto);

          //Define o conteudo e o tipo
          message.setContent(conteudo, mimeType );

          // Send message
          Transport.send(message);
      } catch (Exception ex) {
         ex.printStackTrace();
      }
    }

    private String getHost() {
    	String host = "http://"+request.getLocalName();
    	if(request.getLocalPort()>0 && request.getLocalPort() != 80) {
    		host += ":"+request.getLocalPort();
    	}
    	if(context.getContextPath() != null && !context.getContextPath().equals("")) {
    		host += context.getContextPath();
    	}
    	return host;
    }

}
